package com.data.hijack.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.security.AccessController;
import java.security.PrivilegedAction;

/**
 * Read file content
 * 
 * @author husq
 *
 */
public final class BigFileContentReader {

    private final File file;
    private MappedByteBuffer mByteBuffer;
    private int limit;
    private int position;
    // Line number counter
    private int lineCount;
    private boolean firstLine;
    private boolean tailLine;
    private String firstLineContent;
    private String tailLineContent;

    public boolean isFirstLine() {
        return firstLine;
    }

    public void setFirstLine(boolean firstLine) {
        this.firstLine = firstLine;
    }

    public boolean isTailLine() {
        return tailLine;
    }

    public void setTailLine(boolean tailLine) {
        this.tailLine = tailLine;
    }

    public String getFirstLineContent() {
        return firstLineContent;
    }

    public void setFirstLineContent(String firstLineContent) {
        this.firstLineContent = firstLineContent;
    }

    public String getTailLineContent() {
        return tailLineContent;
    }

    public void setTailLineContent(String tailLineContent) {
        this.tailLineContent = tailLineContent;
    }

    public BigFileContentReader(File file) {
        this.file = file;
        init();
    }

    public BigFileContentReader(String filePath) throws IOException {
        this(new File(filePath));
    }

    /**
     * 
     * @return
     */
    public String readLine() {
        return readLine(0, new byte[] {10}, null);
    }

    /**
     * 
     * @param delimiter
     * @param charset
     * @return
     */
    public String readLine(byte[] delimiter, String charset) {
        return readLine(0, delimiter, charset);
    }

    public String readLine(int lineCapacity, byte[] delimiter, String charset) {
        if (null == charset || "".equals(charset.trim())) {
            charset = "GBK";
        }

        if (position >= limit) {
            return null;
        }

        int delimiterLength = delimiter.length;
        // \r\n or \n\r
        byte firstDelimiter = delimiter[0];
        byte[] src = new byte[lineCapacity == 0 ? 1024 : lineCapacity];
        byte b = 0;
        String destStr;
        try {
            for (int i = 0;; i++) {
                if (position >= limit) {
                    break;
                }
                b = this.mByteBuffer.get();
                position++;
                if (delimiterLength > 1 && b == firstDelimiter) {
                    mByteBuffer.get();
                    position++;
                    break;
                } else if (delimiterLength == 1 && b == firstDelimiter) {
                    break;
                }
                src[i] = b;
            }

            destStr = new String(src, charset).trim();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }

        if (lineCount == 0) {
            this.setFirstLine(true);
            this.setFirstLineContent(destStr);
        } else {
            this.setFirstLine(false);
        }
        if (position == limit - 1) {
            this.setTailLine(true);
            this.setTailLineContent(destStr);
        } else {
            this.setTailLine(false);
        }

        ++lineCount;

        return destStr;
    }

    public byte[] read(int capacity) {
        if (position >= limit) {
            return null;
        }

        byte[] src = new byte[capacity == 0 ? this.limit : capacity];
        mByteBuffer.get(src);

        return src;
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    public void clear() {
        AccessController.doPrivileged(new PrivilegedAction() {
            @SuppressWarnings("restriction")
            public Object run() {
                try {
                    Method getCleanerMethod = mByteBuffer.getClass().getMethod("cleaner", new Class[0]);
                    getCleanerMethod.setAccessible(true);
                    sun.misc.Cleaner cleaner = (sun.misc.Cleaner)getCleanerMethod.invoke(mByteBuffer, new Object[0]);
                    cleaner.clean();
                } catch (Exception e) {
                    e.printStackTrace();
                }

                return null;
            }
        });
    }

    private void init() {
        if (!file.exists() || !file.isFile()) {
            throw new RuntimeException("The file to be read does not exist or not a file");
        }

        FileInputStream fis = null;
        FileChannel fc = null;
        try {
            fis = new FileInputStream(file);
            fc = fis.getChannel();
            mByteBuffer = fc.map(MapMode.READ_ONLY, 0, fc.size());
            limit = mByteBuffer.limit();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                fc.close();
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {

        BigFileContentReader readFile =
            new BigFileContentReader("C:\\Users\\Administrator\\AppData\\Local\\Temp\\TempFile-3162557279303735806");
        byte[] b = readFile.read(0);
        System.out.println(new String(b, "GBK"));
    }
}
